package com.lvmama.dashboard.core.ssh;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;

import org.apache.log4j.Logger;
import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import com.lvmama.dashboard.entity.ShellExec;
import com.lvmama.dashboard.service.ssh.IShellExecService;
import com.lvmama.dashboard.utils.ErrDict;
import com.lvmama.java.rhino.spark.core.ICallback;
import com.lvmama.java.rhino.spark.utils.Constants;
import com.lvmama.rhino.utils.JedisClusterUtils;

import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.Session;
import ch.ethz.ssh2.StreamGobbler;

public class ServerShellHelper implements InitializingBean, DisposableBean {
	private static final Logger logger = Logger.getLogger(ServerShellHelper.class);
	private static final String DEFAULT_CHARSET = Charset.defaultCharset().toString();
	private static String LOCAL_HOST = null;
	private Connection connection;
	private String host;
    private String username;
    private String password;
    private static final Map<Integer, Session> sessionHolder = new HashMap<Integer, Session>();
    @Autowired
	@Qualifier("dashboardThreadPool")
	private ThreadPoolTaskExecutor dashboardThreadPool;
    @Autowired
    private IShellExecService shellExecService;
    
	public ServerShellHelper(String host, String username, String password) {
		this.host = host;
		this.username = username;
		this.password = password;
	}
	
	public List<Integer> getLiveExecId() {
		Iterator<Integer> it = sessionHolder.keySet().iterator();
		List<Integer> result = new ArrayList<Integer>();
		while (it.hasNext()) {
			result.add(it.next());
		}
		return result;
	}
	
	public boolean exec(String cmds, Map<String, String> param) {
		ShellExec shellExec = shellExecService.saveShellExecInfo(host, getLocalHost(), cmds);
		Session session = null;
		InputStream stdOut = null;
		BufferedReader stdOutReader = null;
		InputStream stdErr = null;
		BufferedReader stdErrReader = null;
		BufferedWriter writer = null;
		int execId = shellExec.getExecId();
		try {
			session = connection.openSession();
			sessionHolder.put(execId, session);
			session.execCommand(cmds);
			stdOut = new StreamGobbler(session.getStdout());
			stdErr = new StreamGobbler(session.getStderr());
			stdOutReader = new BufferedReader(new InputStreamReader(stdOut, DEFAULT_CHARSET));
			stdErrReader = new BufferedReader(new InputStreamReader(stdErr, DEFAULT_CHARSET));
			String logDirStr = Constants.getInstance().getValue("remote.shell.current.log.dir");
			File logDir = new File(logDirStr);
			if(!logDir.exists()) {
				logDir.mkdirs();
			}
			File logFile = new File(logDir, shellExec.getShellOutFile().getFileName());
			if(!logFile.exists()) {
				logFile.createNewFile();
			}
			final CountDownLatch waitLatch = new CountDownLatch(2); 
			ICallback callback = new ICallback() {
				@Override
				public void call(Object obj) {
					waitLatch.countDown();
				}
			};
			writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(logFile), DEFAULT_CHARSET));
			SaveShellLogRunner stdOutRunner = new SaveShellLogRunner(execId, stdOutReader, writer, callback);
			SaveShellLogRunner stdErrRunner = new SaveShellLogRunner(execId, stdErrReader, writer, callback);
			dashboardThreadPool.execute(stdOutRunner);
			dashboardThreadPool.execute(stdErrRunner);
			waitLatch.await();
			JedisClusterUtils.getInstance().del("SHELL_EXEC_LOG_" + execId);
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			shellExecService.updateShellExecStatus(execId, Constants.SHELL_STATUS.ERROR, e.getMessage());
			return false;
		} finally {
			try {
				writer.close();
			} catch (IOException e) {
				logger.error(e.getMessage(), e);
			}
			try {
				stdErr.close();
			} catch (IOException e) {
				logger.error(e.getMessage(), e);
			}
			try {
				stdOut.close();
			} catch (IOException e) {
				logger.error(e.getMessage(), e);
			}
			session.close();
			sessionHolder.remove(shellExec.getExecId());
			JedisClusterUtils.getInstance().del("SHELL_EXEC_LOG_" + execId);
		}
		return shellExecService.updateShellExecStatus(shellExec.getExecId(), Constants.SHELL_STATUS.SUCCESS, ErrDict.SHELL.EXEC_SUCCESS.getMsg());
	}
	
	@Override
	public void afterPropertiesSet() throws Exception {
		connection = new Connection(host);
		connection.connect();
        boolean connResult = connection.authenticateWithPassword(username, password);
        if(!connResult) {
        	String errMsg = String.format(ErrDict.SYSTEM.SSH_CONNECTION_FAILED.getMsg(), host);
        	throw new BeanInstantiationException(ServerShellHelper.class, errMsg);
        }
        logger.info("HOST: " + host + " SSH连接成功");
	}

	@Override
	public void destroy() throws Exception {
		if(connection != null) {
			connection.close();
		}
	}
	
	public String getLocalHost() {
		if (LOCAL_HOST == null) {
			synchronized (ServerShellHelper.class) {
				if(LOCAL_HOST == null) {
					try {
						InetAddress inetAddress = InetAddress.getLocalHost();
						if(inetAddress != null) {
							LOCAL_HOST = inetAddress.getHostAddress();
							return LOCAL_HOST;
						}
					} catch (UnknownHostException e) {
						System.out.println("unknown host!");
						LOCAL_HOST = "127.0.0.1";
						return LOCAL_HOST;
					}
				}
			}
		}
		return LOCAL_HOST;
	}
	
}
